<?php
/**
 * @brief Klasa umożliwiająca przeprowadzenie bilansowania.
 * 
 */
class Application_Model_Balancing
{
    private $_msg; /*!< Tworzona wiadomość .*/
    private $_auction_id; /*!<  Identyfikator aukcji. */
    
    /**
     * @brief Konstruktor
     * 
     * Ustawia identyfikator aukcji.
     * 
     * @param int $auction_id 
     */
    function __construct($auction_id) {
        $this->_auction_id = $auction_id;
    }
    
    /**
     * @brief Główna metoda generująca fragmenty pliku M3-XML.
     * 
     * @return array string 
     */
    public function start()
    {
        $auctionMapper = new Application_Model_AuctionMapper();
        $auction = $auctionMapper->find($this->_auction_id);
        
//        $firstOffer = $auction->getStartingOffer();
//        foreach($firstOffer->getComponents() as $component){
//            $commodity = $component->getCommodity();
//            $kinds[] = $commodity->getCommodityType_id();
//        }
        $mapper = new Application_Model_CommodityMapper();
        $commodities = $mapper->fetchByAuction($auction->getAuction_id());
        $kinds = array();

        //lista jednostek rynkowych
        $entities = array();
        
        $vcoms ='';
        $voffers ='';
        $offers = $auction->getOffers();
        
        foreach($offers as $offer){
            $entity = $offer->getMarketEntity_id();
            $pair[$offer->getOffer_id()] = $offer->getMaxVolume();
            $entities[$entity] = $pair;
        }

        
        foreach ($offers as $offer) {
            //$entities[] = $offer->getMarketEntity_id();
            $count = count($entities[$offer->getMarketEntity_id()]);
            $info = array();
            $info['commodities']='';
            $info['offers']='';
            $info['groups']=array();
            $info = $offer->toXML($info);
            $vcoms .= $info['commodities'];
            $voffers .= $info['offers'];
        }
        
        foreach ($entities as $entity_id => $value) {
            $vcoms .= $this->addMarketEntityToken($entity_id);
            $voffers .= $this->addMarketEntityTokenProducer($entity_id);
        }
        
        
        $arcMapper = new Application_Model_NetworkArcMapper();
        $arcs = $arcMapper->fetchAll();
        $nodeMapper = new Application_Model_NetworkNodeMapper();
        $nodes = $nodeMapper->fetchAll();
        
        unset($this->_msg);
        $start = '<?xml version="1.0" encoding="UTF-8"?>
            ';
        //tu trudne :P
        $start .= '<solverArgs>
                    <transformate name="GENERIC" />
                     <data>';
        
        //towary
        $coms = '
                <m3:commodities xsi:schemaLocation="http://www.openM3.org/m3 M3Commodities.xsd"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                    xmlns:m3="http://www.openM3.org/m3">';
        foreach($commodities as $com)
        {
            $coms .= $this->addCommodity($com);
        }
        $coms .= $vcoms;
        $coms .= '</m3:commodities>
            ';
        
        $ckinds = '
                   <m3:commodityKinds xsi:schemaLocation="http://www.openM3.org/m3 M3Commodities.xsd"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                   xmlns:m3="http://www.openM3.org/m3"
                   xmlns:op="http://www.ia.pw.edu.pl/m3/13node">';

        
        $kindMapper = new Application_Model_CommodityTypeMapper();
        foreach ($kinds as $kind_id) {
            $kind = $kindMapper->find($kind_id);
            $ckinds .= $this->addCommodityKind($kind);
        }
        
        $ckinds.= '</m3:commodityKinds>
            ';
        
        $ent = '
               <m3:marketEntities xsi:schemaLocation="http://www.openM3.org/m3 M3MarketEntities.xsd"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                   xmlns:m3="http://www.openM3.org/m3"       
                   xmlns:op="http://www.ia.pw.edu.pl/m3/13node">	
                   ';
        
        $entityMapper = new Application_Model_MarketEntityMapper();
        foreach ($entities as $entity_id => $value) {
            $entity = $entityMapper->find($entity_id);
            $ent .= $this->addMarketEntity($entity);
        }
        
        $ent .= '</m3:marketEntities>
            ';
        
        $ekinds = '
            <m3:marketEntityKinds xsi:schemaLocation="http://www.openM3.org/m3 M3MarketEntities.xsd"
                      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                      xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                      xmlns:m3="http://www.openM3.org/m3"
                      xmlns:hfp="http://www.w3.org/2001/XMLSchema-hasFacetAndProperty"
                      xmlns:op="http://www.openM3.org/m3/13node">
					  
                  <m3:MarketEntityKind id="admin">
                    <m3:name>admin</m3:name>
                    <m3:description>Administrator</m3:description>
                  </m3:MarketEntityKind>

                  <m3:MarketEntityKind id="user">
                    <m3:name>User</m3:name>
                    <m3:description>
                        Zwykły uzytkownik
                    </m3:description>
                  </m3:MarketEntityKind>

                  <m3:MarketEntityKind id="noone">
                    <m3:name>Nic</m3:name>
                    <m3:description>Zbanowany użytkownik</m3:description>
                  </m3:MarketEntityKind>

                </m3:marketEntityKinds>
                ';
        
        $network = '
                <m3:networks xsi:schemaLocation="http://www.openM3.org/m3 M3Networks.xsd"
                     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                     xmlns:m3="http://www.openM3.org/m3"
                     xmlns:op="http://www.ia.pw.edu.pl/m3/13node">
                    <m3:Network>
                    ';
                 
        foreach ($nodes as $node) {
            $network .= $this->addNetworkNode($node);
        }
        
        foreach ($arcs as $arc){
            $network .= $this->addNetworkArc($arc);
        }
        
        $network .= '  
                    </m3:Network>
                </m3:networks>
                ';
        
        $nkinds = '
            <m3:networkKinds xsi:schemaLocation="http://www.openM3.org/m3 M3Networks.xsd"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                 xmlns:m3="http://www.openM3.org/m3"
                 xmlns:op="http://www.openM3.org/m3/13node">

                  <m3:NetworkNodeKind id="arc">
                    <m3:name>ARC</m3:name>
                    <m3:description>ARC</m3:description>
                  </m3:NetworkNodeKind>

                  <m3:NetworkArcKind id="node">
                    <m3:description>node</m3:description>    
                  </m3:NetworkArcKind>

                 </m3:networkKinds>
                 ';
        
        $off = '
            <m3:offers xsi:schemaLocation="http://www.openM3.org/m3 M3Offers.xsd"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                   xmlns:op="http://www.ia.pw.edu.pl/m3/13node"
                   xmlns:m3="http://www.openM3.org/m3">
                   ';
        $off .= $voffers;
        $off .= '
            </m3:offers>
            ';
        
        
        $end = '
                </data>
            </solverArgs>
            ';
        
        $parts = array('coms'=>$coms,'ckind'=>$ckinds,'ent'=>$ent,'ekinds'=>$ekinds,'network'=>$network,'nkinds'=>$nkinds,'off'=>$off);
        return $parts;
    }
    
    
    
    
    
    /**
     * @brief Metoda konwertująca obiekt reprezentujący jednostkę rynkową do formatu M3-XML.
     * 
     * @param Application_Model_MarketEntity $entity
     * @return string 
     */
    private function addMarketEntity(Application_Model_MarketEntity $entity)
    {
        $msg = '
               <m3:MarketEntity dref="'.$entity->getRole().'" id="'.$entity->getLogin().'">
                 <m3:name>Generator located at bus 1 </m3:name>
                 <m3:uri>http://www.'.$entity->getLogin().'.example</m3:uri>
                 <m3:relatedTo ref="node.1" />
               </m3:MarketEntity>
               ';
        return $msg;
    }
    
    /**
     * @brief Metoda tworząca zapis tokenu użytkownika
     * 
     * Metoda generuje zapis towaru tokenu w formie M3XML.
     * Na wejscie podawana jest nazwa uztkownika 
     * 
     * @param int $entityId 
     */
    private function addMarketEntityToken($entityId)
    {
            $msg = '
               <m3:Commodity dref="v" id="vct'.$entityId.'">
                 <m3:description>Virtual commodity token for user '
                        .$entityId.'</m3:description>
               </m3:Commodity>';

        return $msg;
    }
    
    /**
     * @brief Metoda tworząca oferte generujaca token użytkownika
     * 
     * Metoda generuje wirtualna metode z tokenem w formie M3XML.
     * Na wejscie podawana jest nazwa uztkownika 
     * 
     * @param int $entityId 
     */
    private function addMarketEntityTokenProducer($entityId) {
        $xml = '
                <m3:Offer id="ot'.$entityId.'" offeredPrice="0">
                    <m3:description>Virtual excluding offer consumer '.$entityId.'</m3:description>
                    <m3:offeredBy ref="'.$entityId.'" />
                    <m3:volumeRange maxValue="1" minValue="0" />
                    <m3:BundledOffer>
                        <m3:offeredCommodity ref="vct'.$entityId.
                        '" shareFactor="1" />
                    </m3:BundledOffer>
                </m3:Offer>
                ';
        return $xml;
    }
    
    /**
     * @brief Metoda konwertująca obiekt reprezentujący towar do formatu M3-XML.
     * 
     * @param Application_Model_Commodity $commodity
     * @return string 
     */
    private function addCommodity(Application_Model_Commodity $commodity)
    {
        $msg = '
               <m3:Commodity dref="kind.'.$commodity->getCommodityType_id().'" id="com'.$commodity->getCommodity_id().'">
                 <m3:description>'.$commodity->getDescription().'</m3:description>
                 ';
        
        
        if($commodity->getNetworkArc_id() != NULL){
            $msg .= '<m3:availableAt ref="arc.'.$commodity->getNetworkArc_id().'" />
                ';
        } elseif($commodity->getNetworkNode_id() != NULL){
            $msg .= '<m3:availableAt ref="node.'.$commodity->getNetworkNode_id().'" />
                ';
        }
        //@todo kalendarz
         $msg .='<m3:IndependentlyScheduledCommodity deliveryStartTime="'.$commodity->getStartTime().'" deliveryCompletionTime="'.$commodity->getEndTime().'" />
             ';
         $msg .='</m3:Commodity>
             ';
         return $msg;
    }
    
    /**
     * @brief Metoda konwertująca obiekt reprezentujący typ towaru do formatu M3-XML.
     * 
     * @param Application_Model_CommodityType $type
     * @return string 
     */
    private function addCommodityKind(Application_Model_CommodityType $type)
    {
        $msg = '
            <m3:CommodityKind category="'.$type->getCategoryName().'" id="kind.'.$type->getCategory_id().'">
                    <m3:name>'.$type->getName().'</m3:name>
                </m3:CommodityKind>';
        return $msg;
    }
    
    /**
     * @brief Metoda konwertująca obiekt reprezentujący ofertę do formatu M3-XML.
     * 
     * @param Application_Model_Offer $offer
     * @return string 
     */
    private function addOffer(Application_Model_Offer $offer)
    {
        
       $msg = '
                <m3:Offer id="offer'.$offer->getOffer_id().'" offeredPrice="'.$offer->getOfferedPrice().'">
                <m3:description>offer subbmited by consumer '.$offer->getMarketEntity_id().'</m3:description>
                <m3:offeredBy ref="'.$offer->getMarketEntity_id().'" />
                <m3:offerStatus status="m3:offer-open">
                    <m3:durationPeriod endTime="2012-05-22T23:45:00"
                             startTime="2012-05-20T22:00:00" />
                </m3:offerStatus>
                <m3:volumeRange maxValue="'.$offer->getMaxVolume().'" minValue="0" />
                
                    ';
       //<m3:volumeRange maxValue="'.$offer->getMaxVolume().'" minValue="'.$offer->getMinVolume().'" />
       $components  = $offer->getComponents();
       $count = 0;
       $max = 0;
       foreach ($components as $component){
           if( ($quantity = $component->getQuantity())!= 0){
               $count = $count + 1;
               if($max < $quantity){
                   $max = $quantity;
               }
           }
       }
       
       if($count == 1){
           $msg .= '<m3:ElementaryOffer>';
       } else {
           $msg .= '<m3:BundledOffer>';
       }
       
       foreach ($components as $component){
           if($component->getQuantity()){
            $msg .='
                <m3:offeredCommodity ref="com'.$component->getCommodity_id().'" shareFactor="'.$component->getShareFactor()*$component->getQuantity()/$max.'" />
                    ';
           }
       }
       
       if($count == 1){
           $msg .= '</m3:ElementaryOffer>
               ';
       } else {
           $msg .= '</m3:BundledOffer>
               ';
       }
       
       $msg .= '</m3:Offer>
           ';
       return $msg;
    }
    
    /**
     * @brief Metoda konwertująca obiekt reprezentujący połączenie do formatu M3-XML.
     * 
     * @param Application_Model_NetworkArc $arc
     * @return string 
     */
    private function addNetworkArc(Application_Model_NetworkArc $arc)
    {
        $msg = '
                <m3:arc dref="arc" id="arc.'.$arc->getNetworkArc_id().'">  	  	  		  
                   <m3:predecessor ref="node.'.$arc->getPredecessor().'" />
                   <m3:successor ref="node.'.$arc->getSuccessor().'" />
                </m3:arc>
                ';
        return $msg;
    }
    
    /**
     * @brief Metoda konwertująca obiekt reprezentujący lokalizację do formatu M3-XML.
     * 
     * @param Application_Model_NetworkNode $node
     * @return string 
     */
    private function addNetworkNode(Application_Model_NetworkNode $node)
    {
        $msg = '
                <m3:node dref="node" id="node.'.$node->getNetworkNode_id().'">
                  <m3:name>Basic network node no '.$node->getNetworkNode_id().'</m3:name>
                </m3:node>
                ';
        return $msg;
    }
    
    /**
     * @brief Metoda wykonująca bilansowanie.
     * 
     * Dane pozyskane z metody start są przetwarzane najpierw do XML, a potem 
     * do formatu ampl z wykorzystaniem transformaty i procesora XSLT. Model
     * AMPL jest przetwarzany przez glpsol. Wyniki z bilansowania są zapisywane
     * w bazie danych.
     * 
     */
    public function makeMagic($save = false)
    {
        // łączenie plików xml w całość
//        $mysession = new Zend_Session_Namespace('mysession');
//        $measure_time = false;
//        $start = microtime(true);
//        $step1 = $start;
//        $step2 = $start;
//        if (isset($mysession->auctionId)) {
//            $measure_time = true;
//        }
        $res  = $this->start();
        $res = implode(' ', array_values($res));
        
//        if($measure_time) {
//            $end = microtime(true);
//            $mysession->step1[$mysession->counter] = $end - $start; 
//            $step1 = $end;
//        }
        
        //łądowanie transformaty
        $xsl = new DOMDocument();
        $xsl->load('solve/toampl-generic.xsl');
        
        //ładowanie danych
        $m3 = new DOMDocument();
        $m3->loadXML('<data>'.$res.'</data>');
        //file_put_contents('temp/res.txt', $res, FILE_APPEND);
        //przetwarzanie XSLT
        $xslProc = new XSLTProcessor();
        $xslProc->importStylesheet($xsl);
        
        $mod = $xslProc->transformToXml($m3);
        $modFile = 'temp/model'.uniqid().'.mod';
        //return $mod;
        file_put_contents($modFile, $mod);
        
//        if($measure_time) {
//            $end = microtime(true);
//            $mysession->step2[$mysession->counter] = $end - $step1;
//            $step2 = $end;
//        }
        

        //przekaznie danych do glpsol
        $resFile = 'temp/'. uniqid('temp').'.res';
        exec('glpsol --math '.$modFile.' -o '.$resFile);
        
//        if($measure_time) {
//            $end = microtime(true);
//            $mysession->step3[$mysession->counter] = $end - $step2; 
//        }
//        
               
        //przetwarzanie wyników
        $handle = fopen($resFile, 'r'); 
        if($handle == false) {
//            unlink($modFile);
//            return $res;
            throw new Exception('Bląd solvera');
        }
        $display = FALSE;
        $i=0;
        $first = true;
        $nextOffer = null;
        $offer_counter = 0;
        while (!feof($handle)) 
        { 
            
            $data = fgets($handle);
            $data = preg_replace('/\s+/', '|', $data);
            $parts = explode('|', $data);
//            print_r($parts);          
 //           echo $data.'<br/>';
            if($display && empty($parts)){
                $display = FALSE;
            }
            
            if($display && $data == '|') break;
            
            if($display && !empty($parts)){
                if($first){
                    $offer_counter ++;
                    $nextOffer = ltrim($parts[2],'offer[');
                    $nextOffer = rtrim($nextOffer, ']');
                    if(count($parts) < 5){
                        $first = false;
                    } else {
                        $results[$nextOffer] = $parts[4];
                    }
                } else {
                    $results[$nextOffer] = $parts[2];
                    $first = true;
                }
            }
            
            if(isset($parts[0]) && $parts[0] == '------'){
                $i++;
                if($i == 2 ) $display = TRUE;
            }
            
            
        } 
        fclose($handle);
        
//        if($measure_time) {
//            $end = microtime(true);
//            $mysession->step4[$mysession->counter] = $end - $start; 
//            $mysession->step5[$mysession->counter] = $offer_counter; 
//        }
        
        //zapis do wyników do bazy danych
        if($save == TRUE) {
        $mapper = new Application_Model_OfferMapper();
            foreach ($results as $offer_id => $volume) {
                 if (is_numeric($offer_id)) { 
                    $offer = $mapper->find($offer_id);
                    $offer->setAcceptedVolume($volume);
                    $mapper->save($offer);
                    unset($offer);                     
                 }
            }
        }

//        file_put_contents('wyniki.txt', '--------', FILE_APPEND);
//        foreach ($results as $offer_id => $volume) {
//            $s = $offer_id.'|'.$volume."\n";
//            file_put_contents('wyniki.txt', $s, FILE_APPEND);
//       }
        

        unlink($modFile);
        unlink($resFile);
        
        //return $mod;
       // print_r($results);
        return $results;
        
        
    }

}
